/* tibet.c -- Tiny BDF font file editor				(TLP-01)
   Copyright (C) 1999, 2000 Kenichi Handa, Electrotechnical Laboratory, JAPAN.
   Author: Ken'ichi HANDA <handa@etl.go.jp>  */

/* TIBET is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.  */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#ifndef LIBDIR			/* Makefile should supply this macro.  */
#define LIBDIR "/usr/local/share/tibet"
#endif
#ifndef VERSION			/* Makefile should supply this macro.  */
#define VERSION "?.?"
#endif
#define BITMAP "bitmap -gt 1 -grid -sw 16 -sh 16"
#define MENU_FONT "7x14"

/* Information of each character */
typedef struct _charinfo {
  char *startchar;				/* STARTCHAR */
  int swidth_x, swidth_y, dwidth_x, dwidth_y;	/* SWIDTH, DWIDTH */
  int w, h, x, y;				/* BBX */
  unsigned char *bitmap;			/* BITMAP */
  int editing : 1;				/* 1 while editing */
  int full_p : 1;
} charinfo;

#define BYTE(col) ch->bitmap[bytes * j + (col)]
#define BMP(col) (ch->bitmap[(bytes * j) + ((col) / 8)] & (1 << ((col) % 8)))
#define EDITABLE_P(idx) \
  (modified >= 0 && idx >= 0 && chartbl[idx] && !chartbl[idx]->editing)

char *head = NULL;		/* HEADER is stored here.  */
charinfo *chartbl[0x10000];	/* table of pointers to the above structure */
charinfo *ch;			/* temporary working pointer */
char *help_message[100];	/* Array of lines of help message.  */

/* General information of the editing BDF font */
char *bdfname = NULL;		/* name of the BDF file */
int backuped = 0;		/* 1 iff the BDF file is already backuped */
struct bbxinfo { int w, h, x, y; } f;/* FONTBOUNDINGBOX */
int chars;			/* CHARS */
int line_bytes;			/* same as (f.w + 7) / 8; */
char *charset = NULL;		/* CHARSET_REGISTRY field of the font name */
int min_idx, max_idx, cur_idx, last_idx, min_page, max_page, cur_page;
int copy_idx;			/* Index of a copied character. */
int modified;			/* -1:unmodifiable, 0:unmodified, 1:modfied */
int all_full_p;			/* nozero iff all bitmaps fully padded */

/* X related global variables.  */
Display *disp;
int screen;
Window win, root, chiled;
XGCValues gcv;
struct { GC norm, light, mid, dark, rev, l_inv, m_inv, d_inv, temp; } gc;
XEvent event;
KeySym keysym;
XFontStruct *font;
Pixmap pixmap;

/* global variables */
char workdir[FILENAME_MAX + 1];	/* name of working directory */
char linebuf[1024 * 16];	/* line buffer for general use */
FILE *fp;			/* file pointer for general use */

#define FW (font->max_bounds.width)
#define FH (font->max_bounds.ascent + font->max_bounds.descent)
#define FASCENT (font->max_bounds.ascent)

int sep = 6;			/* at least 6 dots for item separation  */
#define INNER		10	/* internal border width */
#define LINE_HEIGHT	(FH + sep)
#define BAR_Y (INNER)
#define BMP_Y (BAR_Y + LINE_HEIGHT)
#define MSG_Y (BMP_Y + (f.h + sep) * ROWS)
#define BMP_HEIGHT ((f.h + sep) * ROWS - sep)
#define BMP_WIDTH ((f.w + sep) * COLS - sep)
#define WIN_HEIGHT (MSG_Y + FH + INNER)
int WIN_WIDTH;
#define MENU_X (INNER * 2 + BMP_WIDTH)

/* X related macros.  */
#define X_GC(gc, gc_inv, fore, back)					     \
  (gc = XCreateGC (disp, RootWindow (disp, screen), 0L, NULL),		     \
   XSetForeground (disp, gc, fore), XSetBackground (disp, gc, back),	     \
   XSetFont (disp, gc, font->fid),					     \
   gc_inv = XCreateGC (disp, RootWindow (disp, screen), 0L, NULL),	     \
   XSetForeground (disp, gc_inv, back), XSetBackground (disp, gc_inv, fore), \
   XSetFont (disp, gc_inv, font->fid))

#define X_Clear(x, y, w, h) XClearArea (disp, win, x, y, w, h, False)

#define X_Draw(gc, x, y, str, len) \
  XDrawImageString (disp, win, gc, x, y, str, len < 0 ? strlen (str) : len)

#define X_Fill(gc, x, y, w, h) XFillRectangle (disp, win, gc, x, y, w, h)

#define DRAW_MENU(i, gc) \
  X_Draw (gc, MENU_X, BMP_Y + LINE_HEIGHT * (i) + FASCENT, menu[i].title, -1)

/* misc macros */
#define COLS		16	/* number of columns of bitmap area */
#define ROWS		8	/* number of rows of bitmap area */
#define PAGE (COLS * ROWS)	/* number of characers in a page */
#define PAGE_HEAD(idx) ((idx) / PAGE)
#define INVALID_IDX -100

#define FATAL(result, fmt, arg) (fprintf (stderr, fmt, arg), exit (result), 0)

#define WARNING(bell, return_flag, fmt, arg)	\
  do {						\
    char buf[1024];				\
    sprintf (buf, fmt, (arg));			\
    show_message (buf, 0);			\
    if (bell) XBell (disp, 0);			\
    if (return_flag) return return_flag;	\
  } while (0)

#define SET_MODIFIED_P(flag)					\
  (modified = flag, X_Draw (gc.m_inv, MENU_X, INNER + FASCENT,	\
			    (flag < 0 ? "-%%-" : flag ? "-**-" : "----"), 4))

#define NEWCPY(src, type, dst, len)	\
  (dst = type malloc (len),		\
   src ? memcpy (dst, src, len) : memset (dst, 0, len))

char hex[256];			/* hex character to hex value */
unsigned char revbits[16] = {	/* Big endian <-> Small endian */
  0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };

/* Scan a decimal integer at *STR and store it at *VAL.  */
getd (int *val, char **str)
{
  register unsigned char *p = (unsigned char *) *str;
  register int n = 0, sign = 1, i;

  while (((i = hex[*p]) == -1 && *p != '-') || i > 9)
    if (!*p++) return 0;
  if (i == -1) sign = -1, p++;
  while ((i = hex[*p++]) >= 0 && i < 10) n = n * 10 + i;
  *str = --p, *val = n * sign;
  return 1;
}

create_bdf (char *fbbx)
{
  if (sscanf (fbbx, "%d%d%d%d", &f.w, &f.h, &f.x, &f.y) != 4)
    return -1;
  bdfname = (char *) malloc (strlen (fbbx) + 5);
  sprintf (bdfname, "%s.bdf", fbbx);
  if (!(fp = fopen (bdfname, "w")))
    FATAL (1, "Can't write to %s\n", bdfname);
  sprintf (linebuf, "STARTFONT 2.1\n\
FONT -new-fixed-medium-r-normal--%d-%d-72-72-C-%d-new-0\n\
SIZE %d 72 72\n\
FONTBOUNDINGBOX %d %d %d %d\n",
	   f.h, f.h * 10, f.w * 10, f.h, f.w, f.h, f.x, f.y);
  fprintf (fp, "%sCHARS 0\nENDFONT\n", linebuf);
  fclose (fp);
  NEWCPY (linebuf, (char *), head, strlen (linebuf) + 1);
  line_bytes = (f.w + 7) / 8;
  chars = 0, min_idx = max_idx = min_page = 0, max_page = 1;
  return 0;
}

read_bdf (int fd)
{
  int size, i, j, idx;
  char *addr, *p, *p1, *p2,*pend;
  struct stat statbuf;

  fstat (fd, &statbuf);
  size = statbuf.st_size;
  if ((int) (p = (char *) mmap (0, size, PROT_READ, MAP_PRIVATE, fd, 0)) == -1)
    FATAL (1, "mmap failed!", "");
  close (fd);

  addr = p; chars = f.w = -1;
  do {
    if (!strncmp (p, "FONTBOUNDINGBOX", 15))
      getd (&f.w, &p), getd (&f.h, &p), getd (&f.x, &p), getd (&f.y, &p);
    else if (!strncmp (p, "CHARS", 5))
      p1 = p, getd (&chars, &p), p2 = p + 1;
    else if (!charset && !strncmp (p, "FONT ", 5))
      {
	pend = strchr (p, '\n');
	for (i = 0; p && p + 1 < pend && i < 13; p = strchr (p + 1, '-'), i++);
	if (p && i == 13)
	  {
	    charset = (char *) malloc (pend - p);
	    for (i = 0, p++; p < pend; i++, p++) charset[i] = tolower (*p);
	    charset[i] = '\0';
	  }
	p = pend;
      }
    if (!(p = strchr (p, '\n'))) break;
    p++;
  } while (strncmp (p, "STARTCHAR", 9) && strncmp(p, "ENDFONT", 7));
  p || FATAL (1, "No STARTCHAR nor ENDFONT in BDF file\n", NULL);
  f.w >= 0 || FATAL (1, "No FONTBOUNDINGBOX in BDF file\n", NULL);

  if (f.y > 0) f.y *= -1;	/* Some fonts contain wrong BBX_Y value.  */
  head = (char *) malloc ((p - p2) + (p1 - addr) + 1);
  strncpy (head, addr, p1 - addr), strncat (head, p2, p - p2);
  chars = 0, min_idx = 0x10000, max_idx = 0;

  all_full_p = 1;
  for (; !strncmp (p, "STARTCHAR", 9); p = strchr (p, '\n') + 1, chars++)
    {
      NEWCPY (NULL, (charinfo *), ch, sizeof (charinfo));
      p1 = strchr (p + 10, '\n') + 1;
      NEWCPY (p + 10, (char *), ch->startchar, p1 - (p + 10));
      ch->startchar[p1 - (p + 10) - 1] = '\0';
      idx = ch->w = -1;		/* -1 means not found.  */

      for (p = p1; strncmp (p, "ENDCHAR", 7); p = strchr (p, '\n') + 1)
	{
	  if (!strncmp (p, "ENCODING", 8))
	    (p += 8, getd (&idx, &p))
	      || FATAL (1, "Invalid ENCODING in %s\n", ch->startchar);
	  else if (!strncmp (p, "BBX", 3))
	    (p += 3, getd (&ch->w, &p) && getd (&ch->h, &p)
	     && getd (&ch->x, &p) && getd (&ch->y, &p))
	      || FATAL (1, "Invalid BBX in %s\n", ch->startchar);
	  else if (!strncmp (p, "BITMAP", 6))
	    {
	      int bytes = (ch->w + 7) / 8;

	      if (ch->w < 0) FATAL (1, "No BBX in %s\n", ch->startchar);
	      ch->bitmap = (unsigned char *) malloc (bytes * ch->h);
	      for (j = 0, p = strchr (p, '\n');
		   j < ch->h; j++, p = strchr (p, '\n'))
		for (i = 0, p++; i < bytes && *p != '\n'; i++, p += 2)
		  BYTE (i) = (revbits[hex[*p]] + revbits[hex[*(p + 1)]] * 16);
	    }
	  else if (!strncmp (p, "SWIDTH", 6))
	    (p += 6, getd (&ch->swidth_x, &p) && getd(&ch->swidth_y, &p))
	      || FATAL (1, "Invalid SWIDTH in %s\n", ch->startchar);
	  else if (!strncmp (p, "DWIDTH", 5))
	    (p += 6, getd (&ch->dwidth_x, &p) && getd(&ch->dwidth_y, &p))
	      || FATAL (1, "Invalid DWIDTH in %s\n", ch->startchar);
	  else
	    FATAL (1, "Invalid record in %s\n", ch->startchar);
	}

      idx >= 0 || FATAL (1, "No ENCODING in\n%s", ch->startchar);
      ch->bitmap || FATAL (1, "No BITMAP in\n%s", ch->startchar);
      (ch->swidth_x > 0) || (ch->swidth_x = 1000);
      (ch->dwidth_x > 0) || (ch->dwidth_x = f.w);
      chartbl[idx] = ch;
      if (idx < min_idx) min_idx = idx;
      else if (idx > max_idx) max_idx = idx;
      if (ch->x < f.x) f.w += f.x - ch->x, f.x = ch->x;
      if (ch->y < f.y) f.h += f.y - ch->y, f.y = ch->y;
      if (ch->x + ch->w > f.x + f.w) f.w = ch->x + ch->w - f.x;
      if (ch->y + ch->h > f.y + f.h) f.h = ch->y + ch->h - f.y;
      ch->full_p
	= ch->x == f.x && ch->y == f.y && ch->w == f.w && ch->h == f.h;
      if (!ch->full_p)
	all_full_p = 0;
    }
  munmap (addr, size);
  if (min_idx == 0x10000) min_idx = 0;
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
  line_bytes = (f.w + 7) / 8;
}

write_bdf ()
{
  int idx, bytes, i;

  if (!backuped++)
    {
      sprintf (linebuf, "/bin/cp %s %s.old", bdfname, bdfname);
      system (linebuf);
    }
  if ((fp = fopen (bdfname, "w")) == NULL)
    WARNING (1, -1, "Can't write to \"%s\"", bdfname);
  WARNING (0, 0, "Saving %s ...", bdfname);
  fprintf (fp, "%sCHARS %d\n", head, chars);
  for (idx = min_idx; idx <= max_idx; idx++)
    if (ch = chartbl[idx])
      {
	fprintf (fp, "STARTCHAR %s\nENCODING %d\n", ch->startchar, idx);
	fprintf (fp, "SWIDTH %d %d\nDWIDTH %d %d\nBBX %d %d %d %d\nBITMAP",
		 ch->swidth_x, 0, ch->dwidth_x, 0, ch->w, ch->h, ch->x, ch->y);
	bytes = (ch->w + 7) / 8;
	for (i = 0; i < bytes * ch->h; i++)
	  {
	    unsigned char bits = ch->bitmap[i];
	    if (!(i % bytes)) fprintf (fp, "\n");
	    fprintf (fp, "%1x%1x", revbits[bits & 0xF], revbits[bits >> 4]);
	  }
	fprintf (fp, "\nENDCHAR\n");
      }
  fprintf (fp, "ENDFONT\n");
  fclose (fp);
  WARNING (0, 0, "Saving %s ... done", bdfname);
  SET_MODIFIED_P (0);
}

update_range (int idx)
{
  if (idx < min_idx) min_idx = idx;
  else if (idx > max_idx) max_idx = idx;
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
  SET_MODIFIED_P (1);
}

charinfo *
make_char (int idx)
{
  NEWCPY (NULL, (charinfo *), ch, sizeof (charinfo));
  chartbl[idx] = ch;
  ch->w = f.w, ch->h = f.h, ch->x = f.x, ch->y = f.y;
  ch->swidth_x = 1000 * ch->w / f.w, ch->dwidth_x = ch->w;
  ch->startchar = (char *) malloc (6);
  sprintf (ch->startchar, "c%02x", idx);
  NEWCPY (NULL, (unsigned char *), ch->bitmap, line_bytes * ch->h);
  if (chars++ == 0) min_idx = max_idx = idx;
  update_range (idx);
  return ch;
}

delete_char (int idx)
{
  write_xbm (idx);
  ch = chartbl[idx];
  free (ch->startchar), free (ch->bitmap), free (ch);
  chartbl[idx] = NULL;
  chars--;
  if (idx == min_idx) while (!chartbl[++min_idx]);
  else if (idx == max_idx) while (!chartbl[--max_idx]);
  min_page = PAGE_HEAD (min_idx), max_page = PAGE_HEAD (max_idx) + 1;
  SET_MODIFIED_P (1);
}

read_xbm (int idx)
{
  int w, h, x, y;
  unsigned char *bitmap;

  sprintf (linebuf, "%s/c%d.xbm", workdir, idx);
  if (XReadBitmapFileData (linebuf, &w, &h, &bitmap, &x, &y) != BitmapSuccess)
    return -1;

  (ch = chartbl[idx]) || (ch = make_char (idx));
  free (ch->bitmap);
  ch->w = w, ch->h = h, ch->x = -x, ch->y = y - ch->h;
  NEWCPY (bitmap, (unsigned char *), ch->bitmap, ((w + 7) / 8) * h);
  XFree (bitmap);
  ch->editing = 0;
  update_range (idx);
  return 0;
}

write_xbm (int idx)
{
  ch = chartbl[idx];
  regularize_bitmap (ch, 0);
  pixmap = XCreateBitmapFromData (disp, win, ch->bitmap, ch->w, ch->h);
  sprintf (linebuf, "%s/c%d.xbm", workdir, idx);
  if (XWriteBitmapFile (disp, linebuf, pixmap,
			ch->w, ch->h, - ch->x, ch->h + ch->y) != BitmapSuccess)
    WARNING (1, 0, "Write %s failed!", linebuf);
  XFreePixmap (disp, pixmap);
}

/* Copy the glyph FROM to TO.  If MERGE is nonzero, new glyph is a
   bitwize-oro of FROM and TO.  */
yank_char (int from, int to, int merge)
{
  int i, j = f.h * line_bytes;

  if (!merge && chartbl[to]) delete_char (to);
  if (!chartbl[to]) make_char (to);
  else regularize_bitmap (chartbl[to], 0);
  regularize_bitmap (chartbl[from], 0);
  for (i = 0; i < j; i++) chartbl[to]->bitmap[i] |= chartbl[from]->bitmap[i];
  write_xbm (to);
}

/* Return 1 if glyphs FROM to TO has space to be shifted to DIR.  */
space_p (int dir, int from, int to)
{
  int bytes, i, j, k;

  for (i = from; i <= to; i++)
    {
      if (!(ch = chartbl[i])) continue;
      bytes = (ch->w + 7) / 8;
      if (dir == 'l')
	{
	  if (ch->x > f.x) continue;
	  for (j = 0; j < ch->h && !(BMP (0)); j++);
	  if (j < ch->h) return 0;
	}
      else if (dir == 'r')
	{
	  if (ch->x + ch->w < f.x + f.w) continue;
	  for (j = 0; j < ch->h && !(BMP (ch->w - 1)); j++);
	  if (j < ch->h) return 0;
	}
      else if (dir == 'u')
	{
	  if (ch->y + ch->h < f.y + f.h) continue;
	  for (j = 0, k = 0; k < bytes && !BYTE (k); k++);
	  if (k < bytes) return 0;
	}
      else
	{
	  if (ch->y > f.y) continue;
	  for (j = ch->h - 1, k = 0; k < bytes && !BYTE (k); k++);
	  if (k < bytes) return 0;
	}
    }
  return 1;
}

shift_glyph (int idx, int dir)
{
  chartbl[idx]->x -= (dir == 'l' ? 1 : dir == 'r' ? -1 : 0);
  chartbl[idx]->y += (dir == 'u' ? 1 : dir == 'd' ? -1 : 0);
  regularize_bitmap (chartbl[idx], 1);
}

bold_glyph (int idx)
{
  int bytes, i, j;

  ch = chartbl[idx], bytes = (ch->w + 7) / 8;
  regularize_bitmap (ch, 0);
  if (!space_p ('r', idx, idx))
    {
      if (!space_p ('l', idx, idx))
	return;
      shift_glyph (idx, 'l');
    }
  for (j = 0; j < ch->h; j++)
    for (i = ch->w - 1; i > 0; i--)
      if ((i + 1 == ch->w || !BMP (i + 1)) && BMP (i - 1))
	BYTE (i / 8) |= (1 << (i % 8));
}

italic_glyph (int idx)
{
  unsigned char *bmp;
  int bytes = line_bytes, i, j, rows;

  ch = chartbl[idx];
  regularize_bitmap (ch, 0);
  bmp = ch->bitmap;
  rows = ch->h / 5;
  for (j = 0; j < rows * 2; j++)
    {
      int bits = 2 - (j / rows);
      for (i = (ch->w - 1) / 8; i > 0; i--)
	BYTE (i) = (BYTE (i) << bits) | (BYTE (i - 1) >> (8 - bits));
      BYTE (i) <<= bits;
    }
  for (j = rows * 3; j < ch->h; j++)
    {
      int bits = (j / rows) - 2;
      for (i = 0; i < (ch->w - 1) / 8; i++)
	BYTE (i) = (BYTE (i) >> bits) | (BYTE (i + 1) << (8 - bits));
      BYTE (i) >>= bits;
    }
}

/* Show the message STR at the message area of window.  If STR is NULL, the
   message area is just erased.  If WAIT != 0, wait user key/click.  */
show_message (char *str, int wait)
{
  X_Clear (INNER, MSG_Y, WIN_WIDTH - INNER * 2, FH);
  if (str) X_Draw (gc.norm, INNER, MSG_Y + FASCENT, str, -1);
  if (wait) while (x_next_event () == -1);
  if (wait < 2) return;
  X_Fill (gc.mid, INNER, BMP_Y, BMP_WIDTH, BMP_HEIGHT);
  draw_page ();
}

/* Go to the next/previous page.  Vacant pages are skipped if FORCE is 0.  */
next_or_prev (int next, int force)
{
  int i = cur_page * PAGE + (next ? PAGE : - 1);

  if (i > max_idx && next && (!force || i >= 0x10000)
      || i <= min_idx && !next && (!force || i < 0))
    return;
  if (force)
    cur_page = PAGE_HEAD (i);
  else
    {
      if (next) while (!chartbl[i]) i++;
      else while (!chartbl[i]) i--;
      cur_page = PAGE_HEAD (i);
    }
  if (cur_idx >= 0)
    cur_idx = cur_page * PAGE + (cur_idx % PAGE);
  draw_page ();
}

struct menu_struct {
  char *title, *key, *guide;
} menu[] = {{"Page", "pn", "Previous or next page"},
	    {"Shrk", "ZZ", "Toggle shrinking"},
	    {"Save", "ss", "Save BDF file"},
	    {"Exit", "qq", "Exit this program"},
	    {"Help", "hh", "Show brief usage"},
	    {"Smpl", "!!", "Show sample text"}};
int menus = (sizeof menu) / (sizeof menu[0]);

/* Display guidance for IDXth char (if IDX >= 0), or (-1-IDX)th menu.  */
show_guide (int idx)
{
  show_message (NULL, 0);
  if (idx == INVALID_IDX)
    return;
  if (idx < 0)
    show_message (menu[-1 - idx].guide, 0);
  else
    {
      int x = INNER + (sep + f.w) * (idx % COLS);
      int y = BMP_Y + (sep + f.h) * ((idx % PAGE) / COLS);
      ch = chartbl[idx];
      if (ch)
	sprintf (linebuf, "%02X (%d,%d,%d,%d) %s",
		 idx, ch->w, ch->h, ch->x, ch->y, ch->startchar);
      else
	sprintf (linebuf, "%02x NO CHARACTER", idx);
      linebuf[BMP_WIDTH / FW] = '\0';
      show_message (linebuf, 0);
    }
}

show_help ()
{
  int i = 0, j, x = INNER + sep / 2;

  do {
    X_Fill (gc.norm, INNER, BMP_Y, BMP_WIDTH, BMP_HEIGHT);
    for (j = 0; help_message[i] && help_message[i][0]; j++, i++)
      X_Draw (gc.rev, x, BMP_Y + FH * j + FASCENT, help_message[i], -1);
    if (help_message[i++])
      show_message ("Any key to next page!!", 1);
  } while (help_message[i]);
  show_message ("Hit any key to dismiss!", 2);
}

show_sample ()
{
  int c, x = INNER + sep / 2, y = BMP_Y + sep, maxy = BMP_Y + BMP_HEIGHT - sep;

  sprintf (linebuf, "%s/%s.txt", LIBDIR, charset ? charset : "");
  if (!charset || (fp = fopen (linebuf, "r")) == NULL)
    sprintf (linebuf, "%s/iso8859-1.txt", LIBDIR);
  if ((fp = fopen (linebuf, "r")) == NULL)
    WARNING (1, 1, "No sample for %s", charset ? charset : "this font");
  X_Fill (gc.rev, INNER, BMP_Y, BMP_WIDTH, BMP_HEIGHT);
  for (c = getc (fp); c != EOF && (y + f.h < maxy); x += f.w, c = getc (fp))
    {
      if (c == '\n' || x + f.w >= INNER + BMP_WIDTH - sep)
	y += f.h, x = INNER + sep / 2 - f.w;
      else
	{
	  if (max_idx > 256) c = c * 256 + getc (fp);
	  if (min_idx > c) c |= 0x8080; /* set MSB */
	  else if (max_idx < c) c &= 0x7F7F; /* reset MSB */
	  if (chartbl[c]) draw_xbm (c, x, y);
	}
    }
  fclose (fp);
  show_message ("Hit any key to dismiss!", 2);
}

/* Pad bitmap to fit in FONTBOUNDINGBOX.  */
regularize_bitmap (charinfo *ch, int force)
{
  unsigned char *bmp = ch->bitmap, *newbmp;
  int bytes = (ch->w + 7) / 8, i, j, x, y;

  if (!force && ch->full_p) return;
  if (ch->w == f.w && ch->h == f.h && ch->x == f.x && ch->y == f.y) return;
  NEWCPY (NULL, (unsigned char *), newbmp, line_bytes * f.h);
  for (j = 0; j < ch->h; j++)
    if ((y = (f.y + f.h) - (ch->y + ch->h - j)) >= 0 && y < f.h)
      for (i = 0; i < ch->w; i++)
	if ((x = ch->x - f.x + i) >= 0 && x < f.w && BMP (i))
	  newbmp[line_bytes * y + (x / 8)] |= (1 << (x % 8));
  ch->w = f.w, ch->h = f.h, ch->x = f.x, ch->y = f.y;
  free (bmp);
  ch->bitmap = newbmp;
  ch->full_p = 1;
  SET_MODIFIED_P (1);
}

/* Remove top/bottom/left/right paddings.  */
shrink_bitmap (charinfo *ch)
{
  int bytes = (ch->w + 7) / 8, i, j, x0, x1, y0, y1, x, y, newbytes;
  unsigned char *newbmp;

  if (bytes == 0) return;
  for (i = 0; i < bytes * ch->h && !ch->bitmap[i]; i++);
  y0 = i / bytes;
  for (i = bytes * ch->h - 1; i >= 0 && !ch->bitmap[i]; i--);
  y1 = i / bytes + 1;
  for (x0 = 0; x0 < ch->w; x0++)
    {
      for (j = 0; j < ch->h && !BMP (x0); j++);
      if (j < ch->h) break;
    }
  for (x1 = ch->w; x1 > x0; x1--)
    {
      for (j = 0; j < ch->h && !BMP (x1 - 1) ; j++);
      if (j < ch->h) break;
    }
  if (y0 == 0 && y1 == ch->h && x0 == 0 && x1 == ch->w)
    return;
  newbytes = (x1 - x0 + 7) / 8;
  all_full_p = ch->full_p = 0;
  if (x1 - x0 == ch->w && y1 - y0 == ch->h) return;
  NEWCPY (NULL, (unsigned char *), newbmp, newbytes * (y1 - y0));
  for (j = y0, y = 0; j < y1; j++, y++)
    for (i = x0, x = 0; i < x1; i++, x++)
      if (BMP (i)) newbmp[newbytes * y + (x / 8)] |=  (1 << (x % 8));
  free (ch->bitmap);
  ch->bitmap = newbmp;
  ch->x += x0, ch->w = x1 - x0, ch->y += ch->h - y1, ch->h = y1 - y0;
  SET_MODIFIED_P (1);
}

/* Draw IDXth glyph at (X, Y).  If X is 0, the location is desided by IDX.  */
draw_xbm (int idx, int x, int y)
{
  ch = chartbl[idx];
  if (x == 0) (x = INNER + (sep + f.w) * (idx % COLS),
	       y = BMP_Y + (sep + f.h) * ((idx % PAGE) / COLS));
  if (x < INNER + BMP_WIDTH && idx == cur_idx)
    gc.temp = (ch && !ch->editing) ? gc.rev : gc.dark;
  else
    gc.temp = (ch && idx != cur_idx && !ch->editing) ? gc.norm : gc.light;

  if (!ch)
    X_Fill (gc.temp, x, y, f.w, f.h);
  else if (ch->w != f.w || ch->h != f.h || ch->x != f.x || ch->y != f.y)
    X_Fill (cur_idx == idx ? gc.dark : gc.light, x, y, f.w, f.h);
  if (!ch || ch->w == 0 || ch->h == 0) return;
  pixmap = XCreateBitmapFromData (disp, win, ch->bitmap, ch->w, ch->h);
  if (gc.temp == gc.dark) gc.temp = gc.d_inv;
  else if (gc.temp == gc.light) gc.temp = gc.l_inv;
  XCopyPlane (disp, pixmap, win, gc.temp, 0, 0, ch->w, ch->h,
	      x + ch->x - f.x, y + (f.h + f.y) - (ch->h + ch->y), 1L);
  XFreePixmap (disp, pixmap);
}

/* Draw all bitmaps of the current page.  */
draw_page ()
{
  int len = (cur_page * PAGE > 256) ? 4 : 2, w, x, i;
  int from = (cur_page < min_page ? cur_page : min_page);
  int to = (cur_page >= max_page ? cur_page + 1: max_page);
  
  w = BMP_WIDTH / (to - from), x = w * (cur_page - from);
  if (w < len * FW)
    w = len * FW, x = (BMP_WIDTH - w) * (cur_page - from) / (to - from - 1);
  if (cur_page + 1 == to)
    w = BMP_WIDTH - x;
  sprintf (linebuf, "%02X", cur_page * PAGE);
  X_Clear (INNER, BAR_Y, BMP_WIDTH, FH);
  X_Fill (gc.norm, INNER + x, BAR_Y, w, FH);
  X_Draw (gc.rev, INNER + x, BAR_Y + FASCENT, linebuf, len);
  for (i = 0; i < PAGE; draw_xbm (cur_page * PAGE + i++, 0, 0));
}

/* Check if (X, Y) is in the square area (X0, Y0)-(X1, Y1).  */
#define IN(x, y, x0, x1, y0, y1) (x >= x0 && x < x1 && y >= y0 && y < y1)

/* Return glyph index at (X, Y) if it's in bitmap area.  If (X, Y) is on
   menu area, return a negative value (-1 - N) for Nth menu.  If (X, Y) is
   not in any of those area, return INVALID_IDX.  */
xy_idx (int x, int y)
{
  if (IN (x, y, INNER, INNER + BMP_WIDTH, BMP_Y, BMP_Y + BMP_HEIGHT))
    return (cur_page * PAGE
	    + (y - BMP_Y) / (f.h + sep) * COLS + (x - INNER) / (f.w + sep));
  if (!IN (x, y, MENU_X, MENU_X + FW * 4, BMP_Y, WIN_HEIGHT - INNER))
    return INVALID_IDX;
  y -= BMP_Y;
  return ((y / LINE_HEIGHT) >= menus || (y % LINE_HEIGHT) >= FH
	  ? INVALID_IDX : -1 - (y / LINE_HEIGHT));
}

/* Initialize the window and X related variables.  */
init_x ()
{
  int i, j, k, l, help_len = 0, help_lines = 0;
  unsigned long white = WhitePixel (disp, screen);
  unsigned long black = BlackPixel (disp, screen);
  Colormap cmap = DefaultColormap (disp, screen);
  XColor exact_def;
  XSetWindowAttributes winattr;

  sprintf (linebuf, "%s/tibet.hlp", LIBDIR);
  if ((fp = fopen (linebuf, "r")) == NULL)
    FATAL (1, "Can't open help file:%s.\n", linebuf);
  for (i = j = 0; i < 98 && fgets (linebuf, sizeof (linebuf), fp) != NULL; i++)
    {
      linebuf[k = strlen (linebuf) - 1] = '\0';
      NEWCPY (linebuf, (char *), help_message[i], k + 1);
      if (linebuf[0] == '\0')
	help_lines = (j > help_lines ? j : help_lines), j = 0;
      else
	help_len = (k > help_len ? k : help_len), j++;
    }
  help_message[i] = help_message[i + 1] = NULL;
  while (BMP_WIDTH < help_len * FW + sep) sep++;
  while (BMP_HEIGHT < help_lines * FH) sep++;
  WIN_WIDTH = INNER * 3 + BMP_WIDTH + (FW * 4 > f.w ? FW * 4 : f.w);

  win = XCreateSimpleWindow (disp, RootWindow (disp, screen),
			     0, 0, WIN_WIDTH, WIN_HEIGHT, 0, 0, white);
  winattr.backing_store = Always;
  XChangeWindowAttributes (disp, win, CWBackingStore, &winattr);
  XSelectInput (disp, win, ButtonPressMask|KeyPressMask|PointerMotionMask);
  XStoreName (disp, win, "tibet");
  X_GC (gc.norm, gc.rev, black, white);
  XParseColor (disp, cmap, "gray80", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.light, gc.l_inv, exact_def.pixel, black);
  XParseColor (disp, cmap, "gray65", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.mid, gc.m_inv, exact_def.pixel, black);
  XParseColor (disp, cmap, "gray50", &exact_def);
  XAllocColor (disp, cmap, &exact_def);
  X_GC (gc.dark, gc.d_inv, exact_def.pixel, white);
  XMapWindow (disp, win);

  X_Fill (gc.mid, 0, 0, WIN_WIDTH, WIN_HEIGHT);
  for (i = 0; i < menus; i++) DRAW_MENU (i, gc.m_inv);
  X_Clear (INNER, MSG_Y, WIN_WIDTH - INNER * 2, FH);
}

int keysym_map[] = {
  XK_Home, '<', XK_Left, 'l', XK_Up, 'u', XK_Right, 'r', XK_Down, 'd',
  XK_Prior, 'p', XK_Next, 'n', XK_End, '>', XK_Begin, '<',
  XK_BackSpace, 'x', XK_Delete, 'x', XK_Insert, 'm', XK_Escape, 'q', 0 };

/* Read the next X event, return ASCII code on KeyPress while
   concering keysym_map, or a negative value on the other events.  */
x_next_event ()
{
  int i;

  XWindowEvent (disp, win,
		ButtonPressMask|KeyPressMask|PointerMotionMask,	&event);
  if (event.type == MotionNotify)
    return -1;
  else if (event.type == ButtonPressMask)
    return ((event.xbutton.button == Button1) - 3);
  XLookupString (&event.xkey, linebuf, 1, &keysym, NULL);
  if (keysym >= XK_space && keysym <= XK_asciitilde)
    return linebuf[0];
  for (i = 0; keysym_map[i]; i += 2)
    if (keysym == keysym_map[i]) return keysym_map[i];
  x_next_event ();
}

/* Interactively get YES or NO answer for the query STR.  */
x_y_or_n (char *str)
{
  int w = FW * strlen (str);
  int x = INNER + BMP_WIDTH / 2, y = BMP_Y + BMP_HEIGHT / 2;
  int x0 = x - FW * 4, x1 = x - FW, x2 = x + FW, x3 = x + FW * 3;
  int y0 = y + FH / 2, y1 = y0 + FH;
  int yn, key;

  X_Fill (gc.norm, x - w / 2 - FW, y - FH * 3 / 2, w + FW * 2, FH * 3);
  X_Draw (gc.rev, x - w / 2, y - FH + FASCENT, str, -1);
  X_Draw (gc.rev, x0, y + FASCENT, "YES  NO", 7);
  XWarpPointer (disp, 0, win, 0, 0, 0, 0, x, y + FH);
  do {
    if ((key = x_next_event ()) == -1)
      {				/* MotionNotify */
	yn = (IN (event.xbutton.x, event.xbutton.y, x0, x1, y0, y1) ? 1
	      : (IN (event.xbutton.x, event.xbutton.y, x2, x3, y0, y1) ? 2
		 : 0));
	X_Draw ((yn == 1 ? gc.norm : gc.rev), x0, y + FASCENT, "YES", 3);
	X_Draw ((yn == 2 ? gc.norm : gc.rev), x2, y + FASCENT, "NO", 2);
      }
    else if (key < 0)
      {				/* ButtonPress */
	if (!yn) continue;
	key = yn == 1 ? 'y' : 'n';
      }
  } while (key != 'y' && key != 'Y' && key != 'n' && key != 'N');
  X_Fill (gc.mid, x - w / 2 - FW, y - FH * 3 / 2, w + FW * 2, FH * 3);
  draw_page ();
  return (key == 'y' || key == 'Y');
}

main (int argc, char **argv)
{
  int key = 0, x, y, i, j, fd, end_flag = 0;
  char *dispname = NULL;

  for (i = 1; i < argc; i++)
    {
      if (!strcmp (argv[i], "-display") && i + 1 < argc)
	dispname = argv[++i];
      else if (!strncmp (argv[i], "-h", 2))
	FATAL (0, "Usage: %s [-v] [-h] [BDFFILE | W+H+X-Y]\n", argv[0]);
      else if (!strncmp (argv[i], "-v", 2))
	FATAL (0, "TIBET Version %s\n", VERSION);
      else
	bdfname = argv[i];
    }
  dispname || (dispname = (char *) getenv ("DISPLAY"));
  dispname || FATAL (1, "X window is required.\n", NULL);
  bdfname ||  FATAL (0, "Usage: %s [-v] [-h] [BDFFILE | W+H+X-Y]\n", argv[0]);

  memset (hex, -1, 256);
  for (i = 0; i < 16; i++) hex["0123456789ABCDEF"[i]] = i;
  for (i = 0; i < 6; i++) hex["abcdef"[i]] = i + 10;

  if ((fd = open (bdfname, O_RDONLY)) >= 0)
    read_bdf (fd);
  else if (create_bdf (bdfname) < 0)
    FATAL (1, "Can't read file %s!\n", bdfname);
  sprintf (workdir, "%s.work", bdfname);

  if (!(disp = XOpenDisplay (dispname)))
    FATAL (1, "Display %s can't be opened.\n", dispname);
  screen = DefaultScreen (disp);
  if (DisplayPlanes (disp, screen) == 1)
    FATAL (1, "Can't run on monochrome screen.\n", "");
  if (!(font = XLoadQueryFont (disp, MENU_FONT)))
    FATAL (1, "Can't open font %s.\n", MENU_FONT);
  init_x ();

  last_idx = min_idx;
  cur_page = PAGE_HEAD (min_idx);
  draw_page ();
  modified = mkdir (workdir, 0755) == 0 ? 0 : -1;
  SET_MODIFIED_P (modified);
  do {
    if (XQueryPointer (disp, win, &root, &chiled, &i, &i, &x, &y, &j) != True)
      continue;
    cur_idx = xy_idx (x, y);
    if (key != -1 || last_idx != cur_idx)
      {
	if (last_idx >= 0) draw_xbm (last_idx, 0, 0);
	else if (last_idx != INVALID_IDX) DRAW_MENU (-1 - last_idx, gc.m_inv);
	if (cur_idx >= 0) draw_xbm (cur_idx, 0, 0);
	else if (cur_idx != INVALID_IDX) DRAW_MENU (-1 - cur_idx, gc.l_inv);
	show_guide (cur_idx);
	last_idx = cur_idx;
      }
    key = x_next_event ();
    if (key == -1)
      continue;					/* MotionNotify */
    else if (key < 0 && cur_idx != INVALID_IDX)	/* ButtonPress */
      key = (cur_idx < 0
	     ? menu[-1 - cur_idx].key[event.xbutton.button != Button1]
	     : (key == -2 ? 'e' : 'g'));
    switch (key) {
    case 'e':			/* Edit */
      if (!EDITABLE_P (cur_idx)) break;
      chartbl[cur_idx]->editing = 1;
      draw_xbm (cur_idx, 0, 0);
      write_xbm (cur_idx);
      sprintf (linebuf, "cd %s; %s c%d.xbm &", workdir, BITMAP, cur_idx);
      system (linebuf);
      continue;
	      
    case 'g':			/* Get glyph */
      if (!chartbl[cur_idx] || read_xbm (cur_idx) < 0) break;
      draw_xbm (cur_idx, 0, 0);
      continue;

    case 'G':			/* Get all glyphs */
      for (i = min_idx; i <= max_idx; i++) read_xbm (i);
      draw_page ();
      continue;

    case 'u': case 'd': case 'l': case 'r': /* Shift glyph */
      if (!EDITABLE_P (cur_idx) || !space_p (key, cur_idx, cur_idx)) break;
      shift_glyph (cur_idx, key);
      draw_xbm (cur_idx, 0, 0);
      continue;

    case 'U': case 'D': case 'L': case 'R': /* Shift glyph all */
      if (!space_p (key = tolower (key), min_idx, max_idx)) break;
      for (i = min_idx; i < max_idx; i++)
	if (EDITABLE_P (i)) shift_glyph (i, key);
      draw_page ();
      continue;

    case 'b': case 'i':		/* Bold or Italic */
      if (!EDITABLE_P (cur_idx)) break;
      if (key == 'b') bold_glyph (cur_idx); else italic_glyph (cur_idx);
      draw_xbm (cur_idx, 0, 0);
      continue;

    case 'B': case 'I':		/* Bold all or Italic all */
      for (i = min_idx; i <= max_idx; i++)
	if (EDITABLE_P (i))
	  {
	    if (key == 'B') bold_glyph (i); else italic_glyph (i);
	  }
      draw_page ();
      continue;

    case 'm':
      if (modified < 0 || chartbl[cur_idx]) break;
      make_char (cur_idx);
      continue;

    case 'x':			/* Delete */
      if (!EDITABLE_P (cur_idx)) break;
      delete_char (cur_idx);
      draw_xbm (cur_idx, 0, 0);
      continue;

    case 'c':			/* Copy */
      if (!EDITABLE_P (cur_idx)) break;
      copy_idx = cur_idx;
      draw_xbm (copy_idx, WIN_WIDTH - INNER - f.w, BMP_Y + BMP_HEIGHT - f.h);
      continue;

    case 'y': case 'Y':		/* Yank */
      if (!EDITABLE_P (cur_idx)) break;
      yank_char (copy_idx, cur_idx, key == 'Y');
      draw_xbm (cur_idx, 0, 0);
      continue;

    case '<': case '>':		/* First or last page */
      cur_idx = key == '<' ? min_idx : max_idx;
      cur_page = PAGE_HEAD (cur_idx);
      draw_page ();
      continue;

    case 'n': case 'N': case 'p': case 'P': /* Previous or next page */
      next_or_prev (tolower (key) == 'n', key == 'N' || key == 'P');
      continue;

    case 'z':			/* toggle shrinking */
      if (!EDITABLE_P (cur_idx)) break;
      ch = chartbl[cur_idx];
      if (ch->full_p) shrink_bitmap (ch); else regularize_bitmap (ch, 0);
      draw_xbm (cur_idx, 0, 0);
      continue;

    case 'Z':			/* toggle shrinking all */
      if (modified < 0) break;
      all_full_p = !all_full_p;
      for (i = min_idx; i <= max_idx; i++)
	if (ch = chartbl[i])
	  {
	    if (all_full_p) regularize_bitmap (ch, 0);
	    else shrink_bitmap (ch);
	  }
      draw_page ();
      continue;

    case 'q': case 17:		/* Quit */
      if (modified <= 0 || x_y_or_n ("Exit without saving?")) end_flag = 1;
      continue;

    case 's':			/* Save */
      if (modified < 0) break;
      write_bdf ();
      continue;

    case 'h': case '?':		/* Show help */
      show_help ();
      continue;

    case '!':			/* Show sample text */
      show_sample ();
      continue;
    }
    XBell (disp, 0);
  } while (!end_flag);

  sprintf (linebuf, "rm -rf %s", workdir);
  if (modified >= 0) system (linebuf);
  exit (0);
} /* This is the 1000th line.  */
